home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Gold Medal Software 3
/
Gold Medal Software - Volume 3 (Gold Medal) (1994).iso
/
prog
/
alertdrv.arj
/
MANUAL.TXT
< prev
next >
Wrap
Text File
|
1994-01-25
|
71KB
|
1,461 lines
Logical Operators
Professional Software Development
& Computer Consultation
707-A Dunbar Avenue
P.O. Box 815
Dunbar WV 25064-0815
Telephone: (304)768-5079
THE ALERTDRIVER C++ CLASS LIBRARY
EXCERPTS FROM USER'S GUIDE
Version 1.00
References to businesses, organizations, and individuals
within this publication are supplied only for informational
purposes and in no way imply or infer endorsement or
recommendation of the products and/or services of Logical
Operators or its associates or affiliates unless explicitly
stated.
All brand names, trademarks, and registered trademarks
appearing throughout this document are properties of their
respective owners. The AlertDriver Class Library source
code, object code, and all associated documentation are
copyrighted 1994 by Logical Operators. All Rights Reserved.
Part number ADUG940125.
Preface
At Logical Operators, we encountered many problems, portability
issues, and maintenance issues when writing error-reporting
code for the reusable libraries used in our applications. Nested
if-then-else code detracted from the clarity of algorithms,
hard-coded error messages caused reusability problems, and
re-routing messages from the screen to other devices (such as
printers) was difficult to say the least.
Our solution to these problems, the AlertDriver Class Library,
avoids all of these problems while providing a new software
library with modularity, encapsulation, a consistent call
interface, and efficient code size and speed. The AlertDriver
Class Library is reusable and extensible to multiple application
environments and hardware configurations. And perhaps most
importantly, this easy-to-use class library provides the
flexibility of multiple error-reporting methods within the same
program while including the capability to easily enable, disable,
or change reporting methods at both compile-time and run-time!
Chapter 2 AlertDriver Class Library Concepts
More Than Just Errors
The AlertDriver Class Library does more than just simple
error-reporting, it alerts the user to virtually any program
condition during execution (hence the name). There are
several types of program conditions, or alerts, which you
may wish to present to the user during program execution:
errors, information, messages, and warnings.
Errors, the most serious types of alerts, occur when a
program detects a state which makes continuation of the
current branch of execution impossible. In interactive
applications, an error usually stops program execution until
acknowledged by the user. A typical error might be: "Cannot
find file C:\YOURFILE.DAT."
Information alerts occur whenever an application informs the
user of a program condition or the occurrence of an event.
Information alerts do not reflect execution problems and
exist only for the convenience of the user. In interactive
applications, an information alert stops program execution
until acknowledged by the user. A typical information alert
might be: "File C:\YOURFILE.DAT was successfully loaded."
Messages also occur when the application informs the user of
a program condition. Like information alerts, messages do
not reflect execution problems and exist only for the
convenience of the user. Unlike information alerts, messages
do not stop program execution in interactive applications.
Once posted, a message remains active until replaced by
another message or cleared by the application. A sample
message might be: "Loading file C:\YOURFILE.DAT..."
Warnings (sometimes referred to as confirmations) are
reported when the application prompts the user for a
decision. Warnings are typically posed as a question with
three possible answers: YES, NO, or CANCEL. Warnings pause
program execution until the user makes a decision, then
continue program execution based upon that decision. A
common warning is "Do you want to overwrite file
C:\YOURFILE.DAT?"
Although the AlertDriver Class Library provides the means to
deal with all of these types of alerts, this chapter will
concentrate only on error handling to simplify the
discussion. Please keep in mind that the concepts of error
handling also apply to each of the other types of alerts.
The AlertableObject Concept
In most class libraries, the majority of classes are
ultimately descendants of a single root class (usually named
something like Object and having little actual
functionality). Most new classes created by users of the
library inherit the properties of an existing class, even if
the class is as basic as Object. Thus, most objects (or
class instantiations) in an application can be referenced
and treated as a lowest common denominator - an Object.
Now let's suppose that the root class has the ability to
report errors it encounters during method executions. If
such a class exists, it becomes easy to derive more
specialized classes while simply inheriting the ability to
report errors. Our programs can invoke the member functions
of an object, knowing that if an error occurs, the object
itself will report the problem to the user! Our programs
only need to be aware of the success or failure of the
member function's execution.
If such a capability could be added to the root class of a
commercial application framework, then the majority of the
framework's classes (and their descendants) would be capable
of reporting their errors to the user in a standardized
manner right out of the box without any additional
programming! Of course, not every programmer would have
access to the framework's source code to implement this, and
we may not want to add additional overhead to such a
rudimentary class anyway. So in the true spirit of object-
oriented programming, we would design a new class which
directly inherits the capabilities of the framework's Object
counterpart and adds some basic error-handling capabilities
(see Example 4).
Example 4 - A sample class declaration which could add error-
handling capabilities to the base class (Object) of an
application framework. The AlertDriver class library creates
an error-handling class in a similar manner.
class AlertableObject : public Object
{
/*add member functions below to
enable error handling*/
...
}; //class AlertableObject
The AlertDriver Class Library includes a class similar to
that in Example 4 and implements the class in such a way
that it can either be used without a class hierarchy or
integrated into practically any C++ class library: new or
existing, commercial or private. We have named this class
AlertableObject: it provides the functionality to alert the
user of errors. By serving as a base class, AlertableObject
provides a common calling interface to our error-handling
member functions. In addition, because AlertableObject
contains a public member function interface, our error-
handling routines are accessible from program code
throughout the application.
Benefiting From AlertableObject
By making AlertableObject a direct descendant of a
framework's root class in the class hierarchy, error-
handling capabilities can easily be inherited by your
classes and their descendants. Of course, classes which are
derived from the root class (and bypass AlertableObject,
like the existing classes in the framework) will not inherit
the error-handling capabilities.
So what other benefits exist from using the AlertableObject
class in an application? Suppose we have a code module which
has just invoked a member function of a descendant of
AlertableObject. Our calling module does not report any
errors which occur within the object's member function
(that's the object's responsibility). However, our calling
module may be interested in determining if the member
function was able to execute successfully, and if not, why.
This simple scenario is an excellent reason to have an
object's member functions return error codes. It is easy to
declare a group of constants which represent error
conditions for each class (see Example 5). Not only does
this practice enforce good programming by removing hard-
coded values from the member functions and calling modules,
but a wide range of values can be represented within the
code returned by each member function. In addition, returned
error codes remove the need for an unnecessary global
variable (such as errno) or class data member to store the
most recent result of a method execution.
Example 5 - Error constants can easily be created and
returned from AlertableObjects.
const long int NOERR = 0;
const long int FILENOTFOUND = 1;
const long int OUTOFMEMORY = 2;
...
class MyObj : public AlertableObject
{
public:
virtual long int MemFunc1(void);
//returns error code
...
}; //class MyObj
By testing the returned error codes, the module calling such
member functions can smoothly recover from errors occurring
within the object while avoiding the "matching-else"
syndrome for error reporting (see Example 6). A pleasant
side-effect is that the source code becomes much more
readable and understandable.
Example 6 - The same algorithm used in Example 1 is
implemented here, using calls to an object's member
functions rather than "plain" function calls. The object
MyObj is derived from class AlertableObject, allowing MyObj
to report errors to the user when they occur. Notice how
much more readable the code below appears than the code of
Example 1.
...
if (MyObj.func1() == NOERR)
if (MyObj.func2() == NOERR)
if (MyObj.func3() == NOERR)
...
Perhaps the biggest benefit of the AlertableObject, however,
lies in its encapsulation. Because the object is self-
contained and able to report its own errors in a platform-
independent manner, it can be linked into multiple
applications, none of which has to deal with reporting the
errors which could occur internally. Imagine how much easier
your next programming project would be if you could simply
plug some of your existing classes together and forget about
handling the errors, confident that they will be handled
automatically! With the AlertDriver Class Library, you can!
Introducing The AlertDriver
The AlertDriver is the class in the hierarchy which provides
the platform- and hardware-dependent error-reporting
capabilities to objects derived from AlertableObject. Each
object derived from AlertableObject uses an AlertDriver
object to perform the actual reporting of each error
message. The AlertDriver object determines how each error
message is reported using the API (application programming
interface) calls and hardware available to the application;
hence, each AlertDriver object is an "error device driver"
for the application environment. Because AlertDrivers are
implemented as classes, any AlertDriver can be subclassed by
the application programmer to change or control its specific
actions.
The AlertDriver class library supplies classes which can be
used to report errors to the screen for interactive
applications, or log errors to a printer or disk file for
programs which normally run without human supervision.
Additional AlertDrivers can be obtained or written for other
platforms and/or hardware devices and linked into programs
written for the AlertDriver Class Library with no change to
the underlying application logic or AlertableObject source
code.
With the concepts described to this point and their inherent
programming power and flexibility, most class libraries
would stop here. However, the AlertDriver Class Library
provides solutions to even more programming problems. An
AlertDriver object itself can reference another AlertDriver
object, effectively chaining, or linking, the AlertDrivers.
By linking the AlertDrivers for an AlertableObject, the
AlertableObject can report a single error in multiple ways
(for example, an error can be reported to the screen and
logged in a disk file). AlertDrivers can be attached and
detached from AlertableObjects in any combination at either
compile-time or run-time for maximum program efficiency and
flexibility.
Each AlertDriver can also have its actions controlled at
either compile-time or run-time via a set of processing
flags. Error-handling, for example, can be temporarily
turned on or off for each AlertDriver without the need to
disconnect or destroy the driver. Other useful processing
flags, which allow a tremendous amount of control over the
AlertDriver during run-time, are also available.
The AlertDriver library also mirrors the concepts of client-
server technology: each AlertableObject can be considered
to be a client, each AlertDriver a server. The client
initiates an action (i.e.: report an error message) and
each server has the ability to perform that action. Multiple
clients can be attached to a single server and a client may
be served by multiple servers.
The ability to connect several AlertableObjects to the same
AlertDriver allows applications to retain all of the
advantages previously discussed while minimizing the amount
of memory overhead needed to implement those features. For
example, in a spreadsheet application, each spreadsheet
object could can be derived from an AlertableObject. If each
cell required its own AlertDriver, then available memory
would decrease rapidly as the spreadsheet grew. However, by
allowing each cell to share a common AlertDriver, the memory
needed to implement AlertDriver processing is held to a
small, fixed amount (the amount needed to allocate the
common AlertDriver). Since all cells in a spreadsheet
normally report errors in the same manner anyway, the
unnecessary allocation of duplicate AlertDriver objects is
avoided.
In the next chapter, we will discuss how easily your
programs can use the AlertDriver Class Library as we step
through a tutorial.
Chapter 3 An AlertDriver Tutorial
Creating New Objects
To use the AlertDriver Class Library, you must include the
following line in your source code file:
#include <alertobj.h>
The ALERTOBJ.H header file contains the declaration for the
AlertableObject class. (The implementation of the
AlertableObject class is supplied in a separate object code
file which is platform-dependent - see the Compiling &
Linking chapter for details on compiling and linking your
code with the supported compilers/platforms.)
To create classes of your own, you simply derive your class
from AlertableObject using public inheritance. (For purposes
of demonstration, we will present a simple, and admittedly
contrived, sample class named Integer, which by itself
doesn't really do anything useful, but will allow us to
demonstrate the proper syntax and implementation of the
AlertDriver library concepts - sample platform-independent
source code implementing Integer is included with the
AlertDriver Class Library.) Your class will automatically
inherit the member functions and data of AlertableObject
(see Example 7).
Example 7 - To derive your own classes, derive your class
from AlertableObject using public inheritance.
class Integer : public AlertableObject
{
...
/*declare class-specific member
functions and data members here*/
...
}; //class Integer
Programmer-Defined Codes, Detection, & Reporting
Once you have inherited the capabilities of the
AlertableObject class, you will want to access that
functionality from your member functions. There are two
basic ways of using the AlertableObject member functions to
alert the user of program conditions: the "quick-and-dirty"
method and the "preferred" method.
The Quick-And-Dirty Reporting Method
The quick-and-dirty method of using the AlertDriver Class
Library is usually used during program testing and/or
debugging. Using this method, your object passes a literal
string to the attached AlertDriver to alert the user of the
program's condition. The quick-and-dirty method retains the
platform-independent output of the AlertDriver, but the
messages themselves are hard-coded into the program, clearly
not the most elegant solution. However, this method is the
quickest and easiest to use for quickly getting a test
program to work or for porting existing programs to the
AlertDriver Class Library. Programs written using the quick-
and-dirty method can easily be ported to the preferred
method later.
To use the quick-and-dirty method, your code calls one of
the Report*() member functions. There are four such
functions (see the Class Library Reference chapter for
details), one for each type of alert: ReportError(),
ReportInfo(), ReportMessage(), and ReportWarning().
(ClearMessage(), a related function used in conjunction with
member function ReportMessage(), clears the last message
posted.)
Example 8 shows how the code in Integer::TestAlertDriver()
makes calls to these functions, passing literal strings to
each; the AlertDriver for the Integer object reports those
strings to the appropriate output device/platform.
Example 8 - Calling the Report*() member functions inherited
from AlertableObject. Each of these inherited functions
passes the string along to the object's AlertDriver.
void Integer::TestAlertDriver(void)
{
...
//test literal strings
ReportMessage("Literal: This is a message string.");
ReportError("Literal: This is an error string.");
ReportWarning("Literal: This is a warning string.",
adsYES);
ClearMessage();
}; //member function Integer::TestAlertDriver
One final note on reporting alerts: the Report*() function
should always be called from the member function where the
alert is first encountered or generated. By consistently
following this suggestion, the code which calls your member
functions can be written to assume that any alerts occurring
within your member functions have already been reported by
the time the function returns.
The Preferred Reporting Method
The preferred method of reporting alerts requires a bit more
work than the quick-and-dirty method, but results in code
which is infinitely more reusable and flexible. We highly
suggest that any new programs and/or objects be written
using the preferred method of alert reporting, as the
preferred method results in programs which are better
structured than programs using the quick-and-dirty method.
Before we explain how to use the preferred method, we must
first discuss the concepts of using programmer-defined codes
for the alerts. Each unique alert for a class can be
assigned a unique constant value for that type of alert
within the class. For example, the error codes for the
Integer object are numbered starting at one (1). Each unique
warning alert for the Integer object is also represented
with a code, with the first warning also being number one
(1). The fact that both alerts have a code numbered one (1)
does not cause a conflict - each type of code is used in its
own context. Using constants (or #defines) to represent
these codes in your program will help both you and other
programmers to identify the different contexts when
reviewing your code (see Example 9). While you may use
negative numbers as alert code constants, do not use zero
(0) as an identifier for an alert code - zero is pre-defined
by the AlertDriver Class Library for each alert type as the
constants errOK, infoOK, msgOK, and warnOK. Alert codes are
represented by long integers.
Example 9 - Define constants to uniquely identify each alert
within a class. Each constant for a particular type of alert
within a class may have the same value as the constant
identifier of another alert type in the same class. Each
constant may also be equal to constants of the same alert
type in other classes.
...
//define alert constants for class Integer
const long errIntDivByZero = 1;
const long errIntOverflow = 2;
const long infoIntConstruct = 1;
const long msgIntDestruct = 1;
const long warnIntChange = 1;
/*legal - different types of alerts*/
...
//define alert constants for class DiskFile
const long errDiskFileNotFound = 1; //legal
const long errDiskFileDiskFull = 1;
/*illegal - same error code (1) defined
twice for same class*/
...
Because errors are the most serious types of alerts, they
should be returned from member functions whenever possible.
This means each member function should return the error code
of any error generated within that function, or return the
error code returned by functions it calls. By adhering to
this principle, the program module which calls your member
functions will always receive an error code denoting the
first error encountered during execution of your function.
Of course, your functions should return errOK if no errors
occur.
The use of programmer-defined alert codes figures
prominently in the preferred method of alert presentation.
Rather than call the Report*() member functions, your member
functions call the inherited Handle*() member functions.
Each Handle*() member function corresponds to one of the
Report*() member functions; the Handle*() member functions
are: HandleError(), HandleInfo(), HandleMessage(), and
HandleWarning().
Each of the Handle*() functions is passed an alert code as a
parameter. This alert code is then passed to one of the
Get*Text() member functions, converted to text, and the text
is passed to one of the Report*() functions. Of course,
there are four Get*Text() member functions: GetErrorText(),
GetInfoText(), GetMessageText(), and GetWarningText().
To use the preferred method of alerting, your object must
declare the appropriate alert constants for alerts which can
be generated within your object. You then override the
Get*Text() functions to convert those constants to strings.
In a well-developed class hierarchy, if your Get*Text()
function receives an alert constant which it cannot convert,
it should call the overridden Get*Text() function, as the
constant may represent an alert which was defined in a
parent class. The original Get*Text() member functions in
AlertableObject produce generic messages (i.e.: "Error 422
encountered.") for unrecognized alert codes. Example 10
shows the implementation of the GetErrorText() member
function for the Integer class.
Example 10 - Implementation of Integer::GetErrorText()
member function. Notice how error codes which are
unrecognized by this class are passed back to the overridden
function.
...
//define error constants for class Integer
const long errIntDivByZero = 1;
const long errIntOverflow = 2;
...
class Integer : public AlertableObject
{
...
public:
virtual void GetErrorText(const long errorCode, char
*text);
...
}; //class Integer
...
void Integer::GetErrorText(const long errorCode, char *text)
{
switch (errorCode)
{
case errIntDivByZero : strcpy(text, "Divide by zero in
Integer object.");
break;
case errIntOverflow : strcpy(text, "Value too large
for Integer object.");
break;
default :
AlertableObject::GetErrorText(errorCode, text);
break;
}; //switch statement to determine error text
}; //member function Integer::GetErrorText
To invoke the use of your strings in your member functions,
you simply call the appropriate Handle*() member function,
passing the correct alert constant. As always, you should
call the appropriate Handle*() member function from the
member function where the alert is first encountered.
Because the Handle*() member functions automatically call
the Get*Text() and Report*() member functions, a simple call
to the appropriate Handle*() member function sets the entire
chain of events into motion.
At first, this may seem like a roundabout way of doing
things, until you realize the amount of flexibility and
reusability that your code gains. Because the Get*Text()
member functions are virtual, you can override them in
descendant classes. If you don't like the text for a
specific alert code, you can derive a new class and return a
different string for the code, even if you don't have the
source code for the original class. The Get*Text() member
functions don't even have to store the alert text in the
program; the functions can be written to retrieve the alert
text from an external text file. Using an external file to
store the strings for each alert allows an application to
use a minimal amount of program memory to store text. In
addition, the text file can later be altered to update
existing alert text without requiring recompilation of the
application. Coupled with the platform-independent output of
the AlertDriver, these capabilities give every
AlertableObject maximum flexibility in presenting alerts to
the user.
Example 11 demonstrates how the overloaded "/" operator
handles errant attempts to divide Integer objects by zero.
Example 11 - The overloaded operator member function
Integer::operator /() calls inherited member function
HandleError() whenever it detects an attempted divide by
zero. Invoking the HandleError() member function causes an
eventual call to the Integer::GetErrorText() member function
shown in Example 10.
int Integer::operator / (int aValue)
{
int result; //holds the function result
if (aValue)
//specified value is not 0
result = val / aValue;
else
{
HandleError(errIntDivByZero);
result = INT_MAX;
}
return result;
}; //overloaded operator Integer::operator /
Changing & Sharing AlertDrivers
All programs linked with the AlertDriver Class Library
automatically gain access to a default AlertDriver
referenced by the defaultAlertDriver pointer (defined in
ALERTDRV.H). This global AlertDriver is usually an
interactive, screen-oriented driver which provides alert
reporting capabilities for the software environment for
which the program was targeted. For example,
defaultAlertDriver may provide text-mode output for a text-
mode program, but will use Windows dialog boxes if the
program has been compiled as a Windows application. Because
defaultAlertDriver is available to be called from any part
of your application, even your non-object code can call
defaultAlertDriver's member functions (see Example 12).
Calling one of the Handle*() member functions of the
AlertDriver class is equivalent to calling one of the
AlertableObject::Report*() member functions (see the Class
Library Reference for details).
Example 12 - Accessing defaultAlertDriver's member functions
from non-object code. Calls made in this way are roughly
equivalent to the quick-and-dirty method of reporting alerts
from within objects.
#include <alertdrv.h>
...
void main(void)
{
...
//test the defaultAlertDriver without an object
defaultAlertDriver->HandleInfo(
"Text from non-object code.");
...
}; //function main()
Each AlertableObject (and derived object) that is created by
your program initially uses the defaultAlertDriver as its
AlertDriver. By using this common AlertDriver, the
AlertableObjects in your program gain all of the benefits of
platform-independent error reporting with a minimum of
code/data overhead. One AlertDriver controls the alerts of
all your program's objects.
There are times, however, when you may want to change this
default behavior, particularly if the default AlertDriver
does not provide the appropriate type of output for your
object. For example, although the default AlertDriver is
usually screen-oriented, your may want your object to log
alerts to a disk file.
You can easily change the type of AlertDriver used by your
object by calling the
AlertableObject::ChangeLinkedAlertDriver() member function.
To call this inherited member function, you create a new
AlertDriver object, then pass its address as a parameter to
the function. Because all AlertDrivers should be allocated
on the heap, this can usually be accomplished with one line
of code. For example, the following line of code will change
the AlertDriver for object MyObj to an AlertDriver which
logs alerts to a text file named "myobj.log":
MyObj.ChangeLinkedAlertDriver(new
TextFileAlertDriver("myobj.log", radFREERESOURCE));
(The radFREERESOURCE constant forces the TextFileAlertDriver
to free (close) the file whenever it is not in use; when the
file is closed (not recording alerts), it can be accessed by
other TextFileAlertDrivers or other applications. See the
Class Library Reference for more details on controlling the
TextFileAlertDriver.)
AlertDrivers allocated by your application can serve several
AlertableObjects at once, just as the default AlertDriver
originally serves all of the objects in your program as they
are created. Example 13 demonstrates how to share one newly
allocated AlertDriver among two objects.
Example 13 - Creating an AlertDriver object and sharing it
among multiple objects. In this case, both objects will log
their alerts to the same text file. Remember, AlertDrivers
which you create within your program should always be
allocated on the heap.
...
AlertDriver *pAlertDriver;
Integer a, b;
...
/*log a and b alerts to the same text file -
other AlertableObjects continue to use
defaultAlertDriver*/
pAlertDriver =
new TextFileAlertDriver("myobjs.log",
radFREERESOURCE);
a.ChangeLinkedAlertDriver(pAlertDriver);
b.ChangeLinkedAlertDriver(pAlertDriver);
...
To completely remove (not replace) the AlertDriver for an
AlertableObject, call member function
ChangeLinkedAlertDriver() with a parameter of NULL; doing
this will remove all alerting capabilities for the
AlertableObject.
The AlertDriver Class Library defines the standard classes
StdAlertDriver and TextFileAlertDriver for all software
platforms and environments. StdAlertDriver uses the standard
C++ iostreams cin, cout, and cerr to report alerts to the
user while TextFileAlertDriver logs alert text to a file
which is specified in its constructor. Additional
AlertDriver classes may be defined for your specific
software environment (for example, class
TurboVisionAlertDriver is defined for programs using
Borland's Turbo Vision class library). See the Class Library
Reference chapter for full details on the AlertDriver
classes which are available in your specific software
environment.
Experienced programmers know that one of the toughest parts
of debugging a program is finding heap allocation errors.
Because all AlertDrivers are allocated on the heap and may
be shared among multiple AlertableObjects, you may be
wondering if there is an easy way to determine when an
AlertDriver is no longer being used and when it is safe to
deallocate an AlertDriver. The answer is simple: you don't
have to worry about deallocating any AlertDrivers, even
those which you allocate! The AlertDriver Class Library
handles it all for you!
Each AlertDriver contains a count of the number of
AlertableObjects using its services. This count is
incremented each time an AlertDriver is "attached" to an
AlertableObject. An AlertableObject is "detached" from an
AlertDriver whenever the AlertableObject is deallocated,
goes out of scope, or is attached to a different AlertDriver
object through member function ChangeLinkedAlertDriver();
each time this happens, the attached AlertDriver's count is
decremented. When the count reaches zero (the AlertDriver is
no longer being used by any object), the AlertDriver is
automatically shutdown and deallocated. What this means to
you as a programmer is that you can allocate as many
AlertDrivers of as many different classes as necessary and
attach them to as many AlertableObjects as you desire with
the understanding that each of the AlertDrivers will be
deallocated as soon as it is no longer being used.
Linking AlertDrivers
Although you can change the AlertDriver used for each of
your objects at any time, this still may not give you the
level of flexibility you need. After all, it's nice to be
able to switch between screen-based alerting and logging
those alerts to a disk file, but what if you wanted to do
both at the same time? With the AlertDriver Class Library,
you can attach several AlertDrivers together in series so
that the same alerts are reported in multiple ways. This
process is known as "linking" the AlertDrivers.
To link one AlertDriver to another, you must call the
ChangeLinkedAlertDriver() member function of the first
AlertDriver object. (The first AlertDriver object is defined
as the AlertDriver which is attached to an AlertableObject,
the remaining AlertDrivers are each attached to the
previously linked AlertDriver. In effect, the AlertDrivers
form a linked list, often referred to as a "chain.") A
common use of linking AlertDrivers is to provide disk file
logging in addition to screen alerting by linking a
TextFileAlertDriver to an AlertableObject's AlertDriver.
This can be most easily accomplished if the
AlertableObject's AlertDriver is the default AlertDriver
(see Example 14).
Example 14 - By linking a TextFileAlertDriver to the default
AlertDriver, all objects using the default AlertDriver will
report their alerts to both the screen and a disk file
(assuming that the default AlertDriver reports alerts to the
screen).
...
//attach a text file AlertDriver to the default driver
defaultAlertDriver->ChangeLinkedAlertDriver(
new TextFileAlertDriver("adtest.log", radFREERESOURCE));
...
It is important to note that when you call
AlertableObject::ChangeLinkedAlertDriver() or
AlertDriver::ChangeLinkedAlertDriver(), you are effectively
replacing all AlertDrivers which are linked to the
object/driver with the AlertDriver you specify. For
illustration, assume that prior to calling the code in
Example 14, defaultAlertDriver is the first of four (4)
AlertDrivers which are linked together. After executing the
code in Example 14, defaultAlertDriver will be the first of
two (2) AlertDrivers in the link. The three AlertDrivers
which were linked to defaultAlertDriver prior to the
ChangeLinkedAlertDriver() call were detached from
defaultAlertDriver (and possibly deallocated if they were
not being used by other objects).
This concept works in the other direction as well. You can
link several AlertDrivers together, than pass the address of
the first one to ChangeLinkedAlertDriver(); the first
AlertDriver will be directly attached to the object/driver
for which the call was made, while all other AlertDrivers in
the link will be indirectly attached to the same
object/driver.
When several AlertDrivers are linked together, the first one
processes the alert from the AlertableObject, then passes
the alert to the next linked AlertDriver. This process
continues until there are no more AlertDrivers in the chain.
Because of this series of actions, it is suggested that you
place only one screen-oriented AlertDriver in any
AlertDriver chain. We also recommend that the screen-
oriented AlertDriver (or any AlertDriver which interacts
with the user) be placed first in the chain - this not only
allows the user to be notified immediately of any alerts,
but also ensures that other AlertDrivers in the chain are
informed of the user's choice. For example, if an
AlertDriver chain contained both a StdAlertDriver and a
TextFileAlertDriver, you should place the StdAlertDriver
first because it interacts with the user. In this case,
warning alerts would be presented to the user first, then
the user's choice would be recorded in the text file. Had
the TextFileAlertDriver been placed first in the chain,
recording the user's choice correctly in the text file would
not be possible.
Modifying AlertDriver Behavior
At times, you may wish to temporarily disable processing of
a certain type of alert or affect the output of an alert for
an AlertableObject without changing or detaching the
AlertDriver. Such changes can be made by altering the
processing flags which are stored in each AlertDriver.
Through the use of AlertableObject member functions
GetAlertProcFlags() and ChangeAlertProcFlags(), you can
obtain and alter the settings of the linked AlertDriver(s)
for an object. (You can directly get/set the processing
flags for an AlertDriver by calling its member functions
GetProcFlags() and ChangeProcFlags() - see the Class Library
Reference chapter for details and syntax.)
The processing flags for an AlertDriver are represented as
bit-mapped constants prefixed with the letters "adf" in the
ALERTDRV.H header file. Four of these constants act as
switches for processing the various types of alerts:
adfERROR, adfINFO, adfMESSAGE, and adfWARNING. When one of
these flags is enabled (on), the AlertDriver processes all
alerts of that type which are passed to it. When a flag is
disabled (off), the AlertDriver ignores alerts of that type,
but still passes the alert to the next linked AlertDriver
(if any).
The return value of AlertableObject::GetAlertProcFlags() is
an unsigned short which holds the value of all flags
currently enabled for the first linked AlertDriver for the
object. You can test to see if a flag is enabled (on) by
performing a binary "AND" operation:
//see if the first-linked AlertDriver for MyObj is currently
processing warnings
if (MyObj.GetAlertProcFlags() & adfWARNING)
{
//warnings are being processed for MyObj
...
}
Example 15 shows how to enable or disable processing for a
specific type of alert using the
AlertableObject::ChangeAlertProcFlags() member function; the
second parameter determines whether the specified flags are
enabled (cfoENABLE), disabled (cfoDISABLE), or intended to
overwrite all of the current flags (cfoOVERWRITE) as well as
whether the changes are made for the first AlertDriver
(cfoDRIVER) or for all linked AlertDrivers (cfoCHAIN).
Please see the Class Library Reference for more details.
Example 15 - Enabling and disabling AlertDriver processing
flags for an Integer object. Notice that when enabling or
disabling specific flags, other flags for the AlertDriver
are not altered. In this example, the first linked
AlertDriver for object "a" is assumed to be a screen-
oriented interactive AlertDriver; this is normally the case.
...
Integer a;
...
/*temporarily disable information alert
processing to the screen for a*/
a.ChangeAlertProcFlags(adfINFO, cfoDRIVER | cfoDISABLE);
/*now perform operations on a which are likely to
generate information alerts*/
...
//enable information alert processing for a
a.ChangeAlertProcFlags(adfINFO, cfoDRIVER | cfoENABLE);
...
The adfALLALERTS flag is a combination of the adfERROR,
adfINFO, adfMESSAGE, and adfWARNING flags.
The adfTIMESTAMP flag controls whether an alert is
timestamped when reported. When enabled, this flag causes
the AlertDriver to generate a string holding the current
date/time and appends the alert text to that string. The
timestamp is formatted with the standard ANSI C function
ctime() (see the Class Library Reference chapter for
details.) A typical timestamped error message output through
a TextFileAlertDriver might look like:
Tue Dec 28 15:18:36 1993
ERROR - Literal: This is an error string.
By this point, you've no doubt realized that a warning is
the only type of alert which needs to return information to
the program. When calling member function
AlertableObject::HandleWarning(), you not only specify a
string containing a question for the user, but you also
indicate the default response to the question (please see
the Class Library Reference chapter for details). If warning
processing is enabled for the object's first linked
AlertDriver, the question is presented to the user and the
response is returned. However, this is one of only three
possible states for the AlertDriver; warning processing may
be disabled for the AlertDriver, or the object may not
currently be attached to an AlertDriver. In the latter case,
AlertableObject::HandleWarning() returns a constant
indicating that no AlertDriver is attached to the object.
But what should be returned in the former case?
By default, if warning processing is disabled for the
AlertDriver, the value returned is the default value you
specified. Your program can assume that the user chose the
default option and execution continues normally. However,
there may be times when you want to know that the user was
not given the opportunity to make a choice. In this case,
you should enable processing flag adfDISCLOSURE for the
AlertDriver. When adfDISCLOSURE is enabled and adfWARNING is
disabled, calls to AlertableObject::HandleWarning() result
in a special constant being returned; this constant
indicates that processing for the current alert was
disabled.
Please see the discussion of
AlertableObject::HandleWarning() in the Class Library
Reference for full details of that member function's
possible return values and how your code should deal with
them.
In most cases, the default settings of each AlertDriver's
processing flags should be adequate for your application.
Remember, when setting the processing flags for an
AlertDriver which is shared between objects, you will be
affecting the alert processing for all of those objects.
What About Exception Handling?
Because exception handling (a fairly new C++ language
feature) is not supported by all C++ compilers (and not
implemented consistently among those compilers which do
support it), we will not attempt to show sample code for
using this feature. However, this does not mean that the
AlertDriver Class Library is incompatible with exception
handling concepts.
Exception handling allows for the separation of a program's
error-generation code from the error-reporting/handling
code. This is implemented through a try block (which
contains a program's/routine's "normal" code) and one or
more catch blocks (which handle the errors occurring within
the try block.) Whenever a program exception (an exceptional
condition, such as an error) occurs within the try block,
the program executes a throw (raises the exception) and
control is passed to one of the catch blocks.
Once the exception reaches the catch block, the exception-
handling code can be reported using an AlertDriver. Even if
your objects have been written to throw exceptions, they
themselves can catch the exceptions, report them using their
linked AlertDriver(s), then re-throw the exceptions to
higher-level code.
If you plan to use exception handling in your C++ programs,
we suggest that you use the AlertDriver Class Library as a
environment- and device-independent means of reporting those
exceptions. The concepts of the AlertDriver Class Library
are a natural supplement to those of exception handling.
Application Design Techniques - A Summary
By now, you've read all about the power and flexibility of
the AlertDriver Class Library. Compiled below are what we
have found to be the most important coding and design
techniques to use when writing applications for the
AlertDriver Class Library.
1.Always invoke the appropriate handling for an alert in
the member function in which the alert is first
generated; this helps make your objects self-contained by
allowing them to process their own alerts.
2.Whenever possible, return error codes from your member
functions. This practice allows calling code to determine
if your member functions were successful.
3.Use the preferred method of alerting whenever possible.
This requires that your class be designed so that alert
constants can be defined early in development, but
rewards you with highly reusable code. By following this
practice, the text for each alert can be stored
externally and/or altered by descendant classes.
4.Use positive or negative numbers to represent alert
constants, but never use zero (0). Zero should indicate
that nothing is wrong.
5.Always try to share AlertDrivers among objects whenever
possible, as this minimizes memory usage. However, you
should give an object its own (non-shared) AlertDriver if
you will be altering its processing flags; this practice
will prevent the inadvertent processing changes of alerts
from other objects.
6.Use the AlertDriver referenced by defaultAlertDriver for
environment-independent alerting for non-object-oriented
code.
7.Always create AlertDrivers on the heap. When they are no
longer used, they will automatically be deallocated by
the AlertableObject(s) which use them.
8.Link AlertDrivers whenever you need to provide alerting
to multiple output devices. Linking can take place at
compile-time or at run-time.
9.Modify an AlertDriver's processing flags whenever you
need to enable or disable the output of certain types of
alerts either temporarily or for the object's lifetime.
Processing flags can be altered at compile-time or at run-
time.
Chapter 4 Compiling & Linking
IMPORTANT: This chapter describes how to compile and
link your programs to use the AlertDriver Class
Library. You should read this section in its
entirety before using the AlertDriver Class
Library.
Supported Compilers & Class Libraries
As of the printing of this manual, the AlertDriver Class
Library has been successfully tested with the following
compilers/class libraries:
Borland C++ v3.1 (text mode, Container Class Library,
Turbo Vision, ObjectWindows)
Microsoft C/C++ v7.0 (text mode)
Other untested compilers/class libraries may be supported in
text mode. You should read the supplied file named
"README.1ST" on the distribution disks for the latest
information which may describe features/updates added after
the printing of this manual.
Header Files
There are four header files associated with the AlertDriver
Class Library:
ADWINDLL.H Prototypes of functions used in Microsoft
Windows helper DLL.
ALERTDRV.H AlertDriver class (and derivatives)
declarations.
ALERTOBJ.H AlertableObject class declaration.
ENVIRON.H Compilation- and target-specific declarations.
You will normally #include the ALERTOBJ.H and/or ALERTDRV.H
header files in your source code files to give the compiler
the necessary class declarations and/or function prototypes.
(The Class Library Reference chapter lists the appropriate
header file for each identifier which your program may need
to access from the AlertDriver Class Library.)
The ADWINDLL.H header file is automatically #included by the
AlertDriver Class Library whenever you compile your program
for the Microsoft Windows platform. You will normally not
need to be concerned with this file.
The ENVIRON.H header file is a special file which contains
all of the compilation- and target-specific declarations
needed to compile your program for different environments.
You may need to replace the first two (2) #define statements
in ENVIRON.H to compile your program for the appropriate
environment (see below). Don't worry: this is extremely
easy to do and the header file itself is clearly documented!
Changing The Compilation Environment #define in ENVIRON.H
The first #define statement in the ENVIRON.H header file
defines the compilation environment and class library to be
used. You should replace the #define with one of the valid
entries listed below. Your choice of compilation environment
will cause the compiler to make several assumptions
(detailed below) about your code. The valid compilation
environment #defines are:
BORLAND_CONTAIN Borland's Container Class Library.
OBJECT.H will be #included and the
AlertDriver Class Library classes will be
derived from Object.
BORLAND_OWL Borland's ObjectWindows Library. Because
Borland's Windows Custom Controls Library is
used to present some alerts in customized
dialog boxes, BWCC.H will be #included
(BWCC.H is supplied with the Borland C++
compiler). OBJECT.H will also be #included
and the AlertDriver Class Library classes
will be derived from Object.
BORLAND_TV Borland's Turbo Vision Class Library.
#defines several Turbo Vision Uses_*
preprocessor definitions, then #includes the
TV.H header file. The AlertDriver Class
Library classes will be derived from TObject.
When you #define this compilation
environment, you should #define your
program's Turbo Vision Uses_* preprocessor
directives before #including any of the
AlertDriver Class Library header files. See
the Turbo Vision documentation for more
details.
NOROOT The AlertDriver Class Library classes will be
compiled without a common root class.
Changing The Target Environment #define In ENVIRON.H
The second #define statement in the ENVIRON.H header file
defines the target environment for the executable file to be
produced. You should replace the #define with one of the
valid entries listed below. Your choice of target
environment will cause the compiler to make several
assumptions (detailed below) about your code. The valid
target environment #defines are:
TARGET_DOS The program will run under the Microsoft DOS
(or compatible) operating system.
TARGET_WINDOWS The program will run under the Microsoft
Windows (or compatible) operating
environment. Header file ADWINDLL.H is
#included (this indirectly #includes
WINDOWS.H). When you #define this target
environment, you should #define your
program's WINDOWS.H preprocessor directives
(if any) before #including any of the
AlertDriver Class Library header files.
Of course, not every combination of compilation environment
and target environment is valid, so make sure that the
combination you choose makes sense (if it doesn't, the
compiler will usually give you an error). For example, the
#defines shown here are illegal:
#define BORLAND_OWL //use ObjectWindows Library
#define TARGET_DOS //ILLEGAL - cannot compile OWL apps as
DOS apps
The correct settings for ENVIRON.H for this example would
be:
#define BORLAND_OWL //use ObjectWindows Library
#define TARGET_WINDOWS //OWL apps must be Windows apps
Linking Your Programs
To link your programs, you must link the appropriate library
to your application in your MAKE or PROJECT file (see your
compiler manual for details on linking). Refer to the lists
below for the appropriate library for your compilation
environment, target environment, and memory model.
IMPORTANT - Make sure you link your programs to the right
library. If you link the wrong library, your
program will not execute.
If you #defined TARGET_DOS in ENVIRON.H as your target
environment:
Link with one of the following libraries, whichever is
appropriate for the setting you #defined for the
compilation environment in ENVIRON.H and your program's
memory model:
ADBRDCCx.LIBBorland C++ Container Classes - There
are six of these files, where x = the
first letter of the memory model
(tiny, small, medium, compact, large,
or huge). For example, ADBRDCCL.LIB
is for use with large memory model
programs.
ADBRDNRx.LIBBorland C++, no root class - There
are six of these files, where x = the
first letter of the memory model
(tiny, small, medium, compact, large,
or huge). For example, ADBRDNRL.LIB
is for use with large memory model
programs.
ADBRDTVL.LIBBorland C++ Turbo Vision - large
memory model.
ADMSDNRx.LIBMicrosoft C/C++, no root class -
There are four of these files, where
x = the first letter of the memory
model (small, medium, compact, or
large). For example, ADMSDNRL.LIB is
for use with large memory model
programs.
If you #defined BORLAND_TV as the compilation
environment, link this file:
TV.LIB Borland's Turbo Vision library
(supplied with the Borland C++
compiler).
If you #defined TARGET_WINDOWS in ENVIRON.H as your target
environment:
Link with one of the following libraries, whichever is
appropriate for the setting you #defined for the
compilation environment in ENVIRON.H and your program's
memory model:
ADBRWCCx.LIBBorland C++ Container Classes - There
are four of these files, where x =
the first letter of the memory model
(small, medium, compact, or large).
For example, ADBRWCCL.LIB is for use
with large memory model programs.
ADBRWNRx.LIBBorland C++, no root class - There
are four of these files, where x =
the first letter of the memory model
(small, medium, compact, or large).
For example, ADBRDNRL.LIB is for use
with large memory model programs.
ADBRWOWx.LIBBorland C++ ObjectWindows Library -
There are four of these files, where
x = the first letter of the memory
model (small, medium, compact, or
large). For example, ADBRWOWL.LIB is
for use with large memory model
programs.
Also link this file:
ADWINDLL.LIBImport library for the file
ADWINDLL.DLL; make sure you
distribute ADWINDLL.DLL with your
finished application!
If you #defined BORLAND_OWL as the compilation
environment, link this file:
BWCC.LIB Import library for Borland's Custom
Control Library for Windows (supplied
with the Borland C++ compiler); make
sure you distribute BWCC.DLL with
your application!
Compiler-Specific Notes
When writing programs which use the AlertDriver C++ Class
Library, you may need to know how the supplied object code
was compiled. The notes below indicate the compiler settings
used to compile the AlertDriver Class Library. You may want
to consider purchasing the Source Code Edition of the
AlertDriver C++ Class Library if you want to recompile the
AlertDriver Class Library with different compiler settings.
Borland C++ v3.1
All object code libraries and DLLs were compiled assuming
that all unsigned char variables should be explicitly
defined (unsigned chars off). Stack checking was turned off.
DOS libraries were compiled with 8086/8088 instructions.
Windows libraries were compiled with 80286 instructions,
target Windows 3.0 and above, and assume the use of the
static versions of the standard, Container Class, and/or
ObjectWindows libraries.
Microsoft C/C++ v7.0
Stack checking was turned off and the libraries were
compiled with 8086 instructions.
Compiling The Sample Programs
To compile any of the sample programs, you should follow all
of the rules and suggestions previously mentioned in this
chapter. Don't forget to correctly #define the compilation-
and target-environment macros in ENVIRON.H!
The sample file INTDEMO.CPP is the main file for the sample
program using the Integer class. (The Integer class is
discussed in the Tutorial Chapter.) To compile this program,
you should compile the INTDEMO.CPP and INTEGER.CPP files,
then link them with the appropriate AlertDriver Library .LIB
file(s). These files can be compiled for any of the
supported compilation- and/or target-environments.
If you want to compile the INTDEMO.CPP sample program as a
Windows application, you should also link the ADWIN.DEF
module definition file to your executable.
Note that the source code for the Integer class (in
INTEGER.CPP), and the INTDEMO.CPP functions which manipulate
the Integer objects, are totally compilation- and target-
environment independent.
Class Hierarchy
In the listing below, derivative classes are indented
underneath their parent class. For example, class
WindowsAlertDriver is derived from class AlertDriver, which
is derived from class AlertDriverLink.
ROOTCLASS
AlertDriverLink
AlertableObject
AlertDriver
RecordingAlertDriver
StreamAlertDriver
TextFileAlertDriver
StdAlertDriver
TurboVisionAlertDriver
WindowsAlertDriver
BWCCAlertDriver
Chapter 6 Creating Your Own AlertDrivers
Design Concepts
For most of your programming projects, the AlertDriver
classes shipped with the AlertDriver Class Library will be
sufficient for your needs. If, however, you would like to
output alerts using operating-system API calls or hardware
devices which are not supported by the standard AlertDriver
Class Library, then you will need to create (subclass) your
own AlertDriver classes. You may also want to subclass the
supplied AlertDrivers to change their default behavior. In
any case, this chapter presents the guidelines which should
be followed while designing and writing new AlertDriver
classes.
Before designing your own custom AlertDriver class, you
should first determine what type of AlertDriver the class
will be. There are two basic types of AlertDriver classes:
interactive (those which can interact with the user) and
recording (those which provide output-only services).
Interactive AlertDriver classes are derived from the
AlertDriver class, while recording AlertDrivers are
subclassed from the RecordingAlertDriver class.
Because interactive AlertDrivers usually report alerts to
the screen, they should be designed to use standard C, C++,
or operating-system API calls whenever possible. Recording
AlertDrivers, however, usually report to hardware devices
(which are somewhat dependent on the operating-system) such
as printers or RS-232C devices. Therefore, recording
AlertDrivers often must be designed to use the API calls
provided by the hardware device manufacturer.
Requirements & Suggestions
To write an AlertDriver class definition, you must have a
good understanding of the API calls necessary to control the
output resource, as well as an understanding of the
class(es) from which you will be subclassing the
AlertDriver. Of course, you must also have a C++ compiler
for your target environment, as well as any prerequisites
for the compiler.
If you are planning to create your own AlertDriver classes,
we recommend that you purchase the Source Code Edition of
the AlertDriver Class Library. By examining the well-
commented source code used to write the supplied AlertDriver
classes, you can gain a much deeper understanding of the
techniques used to write reusable AlertDrivers.
Updating ENVIRON.H
You will have to update the ENVIRON.H header file if you are
trying to do one of the following:
1.port the AlertDriver Class Library to a new compilation
environment (i.e.: use a currently-unsupported hierarchy
class as the root class for the AlertDriver Class
Library)
2.port the AlertDriver Class Library to a new target
environment (i.e.: target your executable code for a
currently-unsupported operating system)
3.redefine one of the macros specifying an AlertDriver
system setting (i.e.: specify a different default
AlertDriver or change the maximum size of an alert text
string)
If you need to update ENVIRON.H, we suggest you first print
the file and study its heavily-commented preprocessor
directives.
Defining Your AlertDriver
To define an AlertDriver, you must first plan to override
the AlertDriver::Report*() member functions. Remember that
for interactive AlertDrivers, member functions
ReportError(), ReportInfo(), and ReportWarning() should halt
program execution until acknowledged by the user. For
recording AlertDrivers, program execution should never be
halted. For full details on the intended actions of the
Report*() member functions for both interactive and
recording AlertDrivers, see the discussions of the
AlertDriver::Report*() member functions in the Class Library
Reference chapter.
If you are writing a recording AlertDriver, you should call
member function GetResourceControl() from your
constructor(s), destructor, and Report*() member functions.
If the resource control mode is radFREERESOURCE or
radHOLDRESOURCE, your object should explicitly capture and
free the resource when appropriate. (For the supplied
TextFileAlertDriver, this is accomplished by opening/closing
the output file.) Review the discussion of the
RecordingAlertDriver data member resourceControl in the
Class Library Reference for more details on controlling the
output resource for a recording AlertDriver.
You may also want to override the
AlertDriver::GetTimestamp() member function to return the
computer's current date/time in a format different from the
default.
Finally, if your AlertDriver encounters an error during
output, you should not attempt to display an error message.
Just clean up any allocated resources and return
immediately.
That's all there is to creating your own AlertDriver
objects! If you have followed these guidelines, your
AlertDriver objects should be usable by any AlertableObject
without changes to the AlertableObject.